/*
 * Make sure that scandir function works OK.
 *
 * Copyright (C) 1998-2019 Toni Ronkko
 * This file is part of dirent.  Dirent may be freely distributed
 * under the MIT license.  For all details and documentation, see
 * https://github.com/tronkko/dirent
 */

/* Silence warning about fopen being insecure (MS Visual Studio) */
#define _CRT_SECURE_NO_WARNINGS

/* Include prototype for versionsort (Linux) */
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <limits.h>

#undef NDEBUG
#include <assert.h>


/* Filter and sort functions */
static int only_readme(const struct dirent *entry);
static int no_directories(const struct dirent *entry);
static int reverse_alpha(const struct dirent **a, const struct dirent **b);


int main(int argc, char *argv[])
{
	struct dirent **files;
	int i;
	int n;

	(void) argc;
	(void) argv;

	/* Initialize random number generator */
	srand((unsigned) time(NULL));

	/* Basic scan with simple filter function */
	{
		/* Read directory entries */
		n = scandir("tests/3", &files, only_readme, alphasort);
		assert(n == 1);

		/* Make sure that the filter works */
		assert(strcmp(files[0]->d_name, "README.txt") == 0);

		/* Release file names */
		for (i = 0; i < n; i++) {
			free(files[i]);
		}
		free(files);
	}

	/* Basic scan with default sorting function */
	{
		/* Read directory entries in alphabetic order */
		n = scandir("tests/3", &files, NULL, alphasort);
		assert(n == 13);

		/* Make sure that we got all the names in the proper order */
		assert(strcmp(files[0]->d_name, ".") == 0);
		assert(strcmp(files[1]->d_name, "..") == 0);
		assert(strcmp(files[2]->d_name, "3zero.dat") == 0);
		assert(strcmp(files[3]->d_name, "666.dat") == 0);
		assert(strcmp(files[4]->d_name, "Qwerty-my-aunt.dat") == 0);
		assert(strcmp(files[5]->d_name, "README.txt") == 0);
		assert(strcmp(files[6]->d_name, "aaa.dat") == 0);
		assert(strcmp(files[7]->d_name, "dirent.dat") == 0);
		assert(strcmp(files[8]->d_name, "empty.dat") == 0);
		assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0);
		assert(strcmp(files[10]->d_name, "sane-1.2.30.dat") == 0);
		assert(strcmp(files[11]->d_name, "sane-1.2.4.dat") == 0);
		assert(strcmp(files[12]->d_name, "zebra.dat") == 0);

		/* Release file names */
		for (i = 0; i < n; i++) {
			free(files[i]);
		}
		free(files);
	}

	/* Custom filter AND sort function */
	{
		/* Read directory entries in alphabetic order */
		n = scandir("tests/3", &files, no_directories, reverse_alpha);
		assert(n == 11);

		/* Make sure that we got file names in the reverse order */
		assert(strcmp(files[0]->d_name, "zebra.dat") == 0);
		assert(strcmp(files[1]->d_name, "sane-1.2.4.dat") == 0);
		assert(strcmp(files[2]->d_name, "sane-1.2.30.dat") == 0);
		assert(strcmp(files[3]->d_name, "sane-1.12.0.dat") == 0);
		assert(strcmp(files[4]->d_name, "empty.dat") == 0);
		assert(strcmp(files[5]->d_name, "dirent.dat") == 0);
		assert(strcmp(files[6]->d_name, "aaa.dat") == 0);
		assert(strcmp(files[7]->d_name, "README.txt") == 0);
		assert(strcmp(files[8]->d_name, "Qwerty-my-aunt.dat") == 0);
		assert(strcmp(files[9]->d_name, "666.dat") == 0);
		assert(strcmp(files[10]->d_name, "3zero.dat") == 0);

		/* Release file names */
		for (i = 0; i < n; i++) {
			free(files[i]);
		}
		free(files);
	}

	/* Trying to read from non-existent directory leads to an error */
	{
		files = NULL;
		n = scandir("tests/invalid", &files, NULL, alphasort);
		assert(n == -1);
		assert(files == NULL);
		assert(errno == ENOENT);
	}

	/* Trying to open file as a directory produces ENOTDIR error */
	{
		files = NULL;
		n = scandir("tests/3/666.dat", &files, NULL, alphasort);
		assert(n == -1);
		assert(files == NULL);
		assert(errno == ENOTDIR);
	}

	/* Sort files using versionsort() */
	{
		files = NULL;
		n = scandir("tests/3", &files, no_directories, versionsort);
		assert(n == 11);

		/*
		 * Make sure that we got all the file names in the proper order:
		 * 1.2.4 < 1.2.30 < 1.12.0
		 */
		assert(strcmp(files[0]->d_name, "3zero.dat") == 0);
		assert(strcmp(files[1]->d_name, "666.dat") == 0);
		assert(strcmp(files[2]->d_name, "Qwerty-my-aunt.dat") == 0);
		assert(strcmp(files[3]->d_name, "README.txt") == 0);
		assert(strcmp(files[4]->d_name, "aaa.dat") == 0);
		assert(strcmp(files[5]->d_name, "dirent.dat") == 0);
		assert(strcmp(files[6]->d_name, "empty.dat") == 0);
		assert(strcmp(files[7]->d_name, "sane-1.2.4.dat") == 0);
		assert(strcmp(files[8]->d_name, "sane-1.2.30.dat") == 0);
		assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0);
		assert(strcmp(files[10]->d_name, "zebra.dat") == 0);

		/* Release file names */
		for (i = 0; i < n; i++) {
			free(files[i]);
		}
		free(files);
	}

	/* Scan large directory */
	{
		char dirname[PATH_MAX+1];
		int i;
		int ok;

		/* Copy name of temporary directory to variable dirname */
#ifdef WIN32
		i = GetTempPathA(PATH_MAX, dirname);
		assert(i > 0);
#else
		strcpy(dirname, "/tmp/");
		i = strlen(dirname);
#endif

		/* Append random characters to dirname */
		for (int j = 0; j < 10; j++) {
			char c;

			/* Generate random character */
			c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26];

			/* Append character to dirname */
			assert(i < PATH_MAX);
			dirname[i++] = c;
		}

		/* Terminate directory name */
		assert(i < PATH_MAX);
		dirname[i] = '\0';

		/* Create directory */
#ifdef WIN32
		ok = CreateDirectoryA(dirname, NULL);
		assert(ok);
#else
		ok = mkdir(dirname, 0700);
		assert(ok == /*success*/0);
#endif

		/* Create one thousand files */
		assert(i + 5 < PATH_MAX);
		for (int j = 0; j < 1000; j++) {
			FILE *fp;

			/* Construct file name */
			dirname[i] = '/';
			dirname[i+1] = 'z';
			dirname[i+2] = '0' + ((j / 100) % 10);
			dirname[i+3] = '0' + ((j / 10) % 10);
			dirname[i+4] = '0' + (j % 10);
			dirname[i+5] = '\0';

			/* Create file */
			fp = fopen(dirname, "w");
			assert(fp != NULL);
			fclose(fp);

		}

		/* Cut out the file name part */
		dirname[i] = '\0';

		/* Scan directory */
		n = scandir(dirname, &files, no_directories, alphasort);
		assert(n == 1000);

		/* Make sure that all 1000 files are read back */
		for (int j = 0; j < n; j++) {
			char match[100];

			/* Construct file name */
			match[0] = 'z';
			match[1] = '0' + ((j / 100) % 10);
			match[2] = '0' + ((j / 10) % 10);
			match[3] = '0' + (j % 10);
			match[4] = '\0';

			/* Make sure that file name matches that on the disk */
			assert(strcmp(files[j]->d_name, match) == 0);

		}

		/* Release file names */
		for (int j = 0; j < n; j++) {
			free(files[j]);
		}
		free(files);
	}

	printf("OK\n");
	return EXIT_SUCCESS;
}

/* Only pass README.txt file */
static int only_readme(const struct dirent *entry)
{
	int pass;

	if (strcmp(entry->d_name, "README.txt") == 0) {
		pass = 1;
	} else {
		pass = 0;
	}

	return pass;
}

/* Filter out directories */
static int no_directories(const struct dirent *entry)
{
	int pass;

	if (entry->d_type != DT_DIR) {
		pass = 1;
	} else {
		pass = 0;
	}

	return pass;
}

/* Sort in reverse direction */
static int reverse_alpha(const struct dirent **a, const struct dirent **b)
{
	return strcoll((*b)->d_name, (*a)->d_name);
}
